module Ch04Test(tests) where

import Test.HUnit
import TestHelper

import Ch04

import Data.List

safeHeadTests =
    TestList["empty" ~: (Nothing :: Maybe Int) @=? safeHead [],
             "one" ~: Just 1 @=? safeHead [1],
             "many" ~: Just 3 @=?safeHead [3,4,5]]

safeTailTests =
    TestList["empty" ~: (Nothing :: Maybe [Int]) @=? safeTail [],
             "one" ~: (Just [] :: Maybe [Int]) @=? safeTail [1],
             "two" ~: Just [2] @=? safeTail [1,2],
             "many" ~: Just [7,6,5] @=? safeTail [8,7,6,5]]

safeLastTests =
    TestList["empty" ~: (Nothing :: Maybe Int) @=? safeLast [],
             "one" ~: Just 1 @=? safeLast [1],
                       "many" ~: Just 5 @=?safeLast [3,4,5]]

safeInitTests =
    TestList["empty" ~: (Nothing :: Maybe [Int]) @=? safeInit [],
             "one" ~: (Just [] :: Maybe [Int]) @=? safeInit [1],
             "two" ~: Just [1] @=? safeInit [1,2],
             "many" ~: Just [8,7,6] @=? safeInit [8,7,6,5]]

splitWithTests =
    TestList["empty" ~: ([] :: [[Int]]) @=? splitWith even [],
             "no match" ~: [[1,3,5,7]] @=? splitWith even [1,3,5,7],
             "first matches" ~: [[3,5]] @=? splitWith even [2,3,5],
             "second matches" ~: [[1],[3,5]] @=? splitWith even [1,2,3,5],
             "two matches" ~: [[1],[3],[5]] @=?
                           splitWith even [1,2,3,4,5],
             "multiple matches" ~: [[1],[3],[5],[7]] @=?
                                splitWith even [1..7],
             "two misses between match" ~: [[1,1],[3,3,3],[5,5]] @=?
                                        splitWith even [1,1,2,3,3,3,4,5,5],
             "consecutive matches" ~: [[1],[5]] @=? splitWith even [1,2,4,5],
             "matches at end" ~: [[1]] @=? splitWith even [1,2,4],
             "everything matches" ~: [] @=? splitWith even [2,4,6]]

firstWordsTests =
    TestList["empty" ~: "" @=? firstWords "",
             "one line" ~: "first\n" @=? firstWords "first words\n",
             "two lines" ~: "one\ntwo\n" @=?
                         firstWords "one line\ntwo lines\n",
             "many lines" ~: "one\ntwo\nthree\nfour\n" @=?
                          firstWords
                          "one\ntwo words\nthree more words\nfour\n",
             "initial whitespace" ~: "initial\n" @=?
                                  firstWords " initial whitespace\n",
             "no trailing newline" ~: "one\ntwo\n" @=?
                                   firstWords "one\ntwo",
            "empty line in middle" ~: "foo\n\nbar\n" @=?
                                   firstWords "foo\n\nbar\n"]

transposeLinesTests =
    TestList["empty" ~: "" @=? transposeLines "",
             "one line" ~: "o\nn\ne\n" @=? transposeLines "one\n",
             "two lines" ~: "hw\neo\nlr\nll\nod\n" @=?
                         transposeLines "hello\nworld\n",
             "unequal lengths" ~: "eab\na a\nt g\n  e\n  l\n" @=?
                               transposeLines "eat\na\nbagel\n"]

asIntTests =
    TestList["zero" ~: 0 @=? asInt "0",
             "one" ~: 1 @=? asInt "1",
             "multidigit" ~: 1798 @=? asInt "1798",
             "negative" ~: -1798 @=? asInt "-1798",
             "empty" ~: 0 @=? asInt "",
             "negative empty" ~: 0 @=? asInt "-",
             "bad character" @! asInt "2.7",
             "overflow" ~: 564616105916946374 @=? asInt "314159265358979323846"]

asIntEitherTests =
    TestList["zero" ~: Right 0 @=? asInt_either "0",
             "one" ~: Right 1 @=? asInt_either "1",
             "multidigit" ~: Right 1798 @=? asInt_either "1798",
             "negative" ~: Right (-1798) @=? asInt_either "-1798",
             "empty" ~: Right 0 @=? asInt_either "",
             "negative empty" ~: Right 0 @=? asInt_either "-",
             "bad character" ~: Left "non-digit '.'" @=? asInt_either "2.7",
             -- The book says to show "non-digit 'o'" here, but that
             -- makes the code more complicated for no apparent gain.
             "multiple bad characters" ~: Left "non-digit 'f'" @=?
                                       asInt_either "foo"]

concatTests catter =
    TestList["none" ~: ([] :: [Int]) @=? catter [],
             "one" ~: [1, 2] @=? catter [[1, 2]],
             "multiple" ~: [1,2,3,4,5,6,7] @=? catter [[1,2],[3],[4,5,6,7]],
             "empty in middle" ~: [1,2,3] @=? catter [[1,2],[],[3]]]

takeWhileTests taker =
    TestList["empty" ~: ([] :: [Int]) @=? taker odd [],
             "one match" ~: [1] @=? taker odd [1],
             "one no match" ~: [] @=? taker odd [2],
             "multiple matches" ~: [1,3,5] @=? taker odd [1,3,5],
             "stop after first failure" ~: [1,3] @=? taker odd [1,3,4,5,7]]

groupByTests grouper =
    TestList["empty" ~: ([] :: [[Int]]) @=? grouper sameParity [],
             "one element" ~: [[1]] @=? grouper sameParity [1],
             "two matches" ~: [[1,3]] @=? grouper sameParity [1,3],
             "two different" ~: [[1],[2]] @=? grouper sameParity [1,2],
             "repeated matches" ~: [[1,3,5]] @=? grouper sameParity [1,3,5],
             "repeated mismatches" ~: [[1],[2],[3]] @=?
                                   grouper sameParity [1,2,3],
             "2 + 1" ~: [[1,3], [4]] @=? grouper sameParity [1,3,4],
             "2 + 2" ~: [[1,3], [4,6]] @=? grouper sameParity [1,3,4,6],
             "2 + 1 + 2" ~: [[1,3], [4], [5,7]] @=?
                         grouper sameParity [1,3,4,5,7]]
    where
      sameParity x y = even (x - y)

finiteAnyTests anyFunc =
    TestList["empty" ~: False @=? anyFunc odd [],
             "one match" ~: True @=? anyFunc odd [1],
             "one non-match" ~: False @=? anyFunc odd [2],
             "y n" ~: True @=? anyFunc odd [1,2],
             "n y" ~: True @=? anyFunc odd [2,1],
             "y y" ~: True @=? anyFunc odd [1,3],
             "n n" ~: False @=? anyFunc odd [2,4],
             "n y n" ~: True @=? anyFunc odd [0,1,2],
             "n n n" ~: False @=? anyFunc odd [2,4,6]]

anyTests anyFunc =
    TestList[finiteAnyTests anyFunc,
             "infinite y" ~: True @=? anyFunc odd [1,3..],
             "infinite n y" ~: True @=? anyFunc odd [0,1..]]

nonemptyCycleTests cycler =
    TestList["one element" ~: [1,1,1] @=? take 3 (cycler [1]),
             "many elements" ~: [1,2,3,1,2,3,1] @=? take 7 (cycler [1,2,3])]

cycleTests cycler =
    TestList[nonemptyCycleTests cycler,
             "empty list" @! cycler []]

wordsTests wordsFunc =
    TestList["empty" ~: ([] :: [[Char]]) @=? wordsFunc "",
             "one word" ~: ["foo"] @=? wordsFunc "foo",
             "two words" ~: ["foo", "bar"] @=? wordsFunc "foo bar",
             "multiple spaces" ~: ["foo", "bar"] @=? wordsFunc "foo  bar",
             "trailing whitespace" ~: ["foo"] @=? wordsFunc "foo  ",
             "initial whitespace" ~: ["foo"] @=? wordsFunc "  foo",
             "complex" ~: ["foo", "bar", "baz"] @=?
                       wordsFunc "\n foo\t bar baz\n\n"]

unlinesTests unlinesFunc =
    TestList["empty" ~: "" @=? unlinesFunc [],
             "one line" ~: "foo bar\n" @=? unlinesFunc ["foo bar"],
             "many lines" ~: "1\n2\n3\n" @=? unlinesFunc["1", "2", "3"]]

tests = TestList[safeHeadTests, safeTailTests, safeLastTests, safeInitTests,
                 splitWithTests, firstWordsTests, transposeLinesTests,
                 asIntEitherTests, concatTests concat, concatTests myConcat,
                 takeWhileTests takeWhile, takeWhileTests takeWhileRecurse,
                 takeWhileTests takeWhileFold, groupByTests groupBy,
                 groupByTests myGroupByRecurse, groupByTests myGroupByFold,
                 anyTests any, anyTests myAny, finiteAnyTests myAny2,
                 anyTests myAny3, anyTests myAny4, finiteAnyTests myAny5,
                 finiteAnyTests myAny6, cycleTests cycle,
                 nonemptyCycleTests myCycle, cycleTests myCycle2,
                 wordsTests words, wordsTests myWords,
                 unlinesTests unlines, unlinesTests myUnlines,
                 unlinesTests myUnlines2]
